#include "Number.h"
#include "Mystrcat.h"
#include <gc/gc.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <assert.h>

void numbercpy(Number * dest, const Number * src){
	assert(dest != NULL);
	assert(src != NULL);
	if (dest == src){
		return;
	}
	dest->type = src->type;
	dest->real = src->real;
	fraccpy(&(dest->fraction), &(src->fraction));
}

void inttonum(Number * dest, const int src){
	assert(dest != NULL);
	dest->type = FRACTION;
	inttofrac(&(dest->fraction), src);
}

void linttonum(Number * dest, const long long int src){
	assert(dest != NULL);
	dest->type = FRACTION;
	linttofrac(&(dest->fraction), src);
}

void doubletonum(Number * dest, const double src){
	assert(dest != NULL);
	dest->type = REAL;
	dest->real = src;
}

void ldoubletonum(Number * dest, const long double src){
	assert(dest != NULL);
	dest->type = REAL;
	dest->real = src;
}

int numbercmp(const Number * first, const Number * second){
	Real one;
	Real two;
	assert(first != NULL);
	assert(second != NULL);
	if (first->type == FRACTION && second->type == FRACTION){
		return fraccmp(&(first->fraction), &(second->fraction));
	}else{
		one = numtodouble(first);
		two = numtodouble(second);
		if (one >two){
			return 1;
		}else if (one < two){
			return -1;
		}else{
			return 0;
		}
	}
}

int number_is_int(const Number * number){
	assert(number != NULL);
	if(number->type == FRACTION && number->fraction.q == 1){
		return 1;
	}else{
		return 0;
	}
}


long double numtodouble(const Number * number){
	long double num;
	long double den;
	long double val;
	assert(number != NULL);

	if (number->type == REAL){
		return number->real;
	}else{
		num = number->fraction.p;
		den = number->fraction.q;
		val = num/den;
		return (number->fraction.sign == POSITIVE)?val:-val;
	}
}

void numberopp(Number * dest, const Number * src){
	assert(dest != NULL);
	assert(src != NULL);
	dest->type = src->type;
	if(dest->type == FRACTION){
		dest->type = FRACTION;
		fracopp(&(dest->fraction), &(src->fraction));
	}else{
		dest->type=REAL;
		dest->real = -src->real;
	}
}

void numberinv(Number * dest, const Number * src){
	assert(dest != NULL);
	assert(src != NULL);
	dest->type = src->type;
	if(dest->type == FRACTION){
		dest->type = FRACTION;
		fracinv(&(dest->fraction),&(src->fraction));
	}else{
		dest->type = REAL;
		dest->real = 1/src->real;
	}
}

void numbersin(Number * dest, const Number * src){
	ldoubletonum(dest, sinl(numtodouble(src)));
}

void numbercos(Number * dest, const Number * src){
	ldoubletonum(dest, cosl(numtodouble(src)));
}

void numbertan(Number * dest, const Number * src){
	ldoubletonum(dest, tanl(numtodouble(src)));
}

void numberln(Number * dest, const Number * src){
	ldoubletonum(dest, logl(numtodouble(src)));
}

/*Next four (or five) functions are very similar. So be it. */
void numberplus(Number * dest, const Number * first, const Number * second){
	long double one;
	long double two;
	assert(dest != NULL);
	assert(first != NULL);
	assert(second != NULL);
	if(first->type == FRACTION && second->type == FRACTION){
		dest->type = FRACTION;
		fracplus(&(dest->fraction), &(first->fraction), &(second->fraction));
	}else{
		one = numtodouble(first);
		two = numtodouble(second);
		dest->type = REAL;
		dest->real = one + two;
	}
}

void numberminus(Number * dest, const Number * first, const Number * second){
	long double one;
	long double two;
	assert(dest != NULL);
	assert(first != NULL);
	assert(second != NULL);
	if(first->type == FRACTION && second->type == FRACTION){
		dest->type = FRACTION;
		fracminus(&(dest->fraction), &(first->fraction), &(second->fraction));
	}else{
		one = numtodouble(first);
		two = numtodouble(second);
		dest->type = REAL;
		dest->real = one - two;
	}
}

void numbermul(Number * dest, const Number * first, const Number * second){
	long double one;
	long double two;
	assert(dest != NULL);
	assert(first != NULL);
	assert(second != NULL);
	if(first->type == FRACTION && second->type == FRACTION){
		dest->type = FRACTION;
		fracmul(&(dest->fraction), &(first->fraction), &(second->fraction));
	}else{
		/*dest->type = REAL; BUUUUUUUUUUUUUUUUUG - same for other functions*/
		one = numtodouble(first);
		two = numtodouble(second);
		dest->type = REAL;
		dest->real = one * two;
	}
}

void numberdiv(Number * dest, const Number * first, const Number * second){
	long double one;
	long double two;
	assert(dest != NULL);
	assert(first != NULL);
	assert(second != NULL);
	if(first->type == FRACTION && second->type == FRACTION){
		dest->type = FRACTION;
		fracdiv(&(dest->fraction), &(first->fraction), &(second->fraction));
	}else{
		one = numtodouble(first);
		two = numtodouble(second);
		dest->type = REAL;
		dest->real = one / two;
	}
}

void numberpow(Number * dest, const Number * first, const Number * second){
	long double one;
	long double two;
	Integer i;
	assert(dest != NULL);
	assert(first != NULL);
	assert(second != NULL);
	if((second->type == FRACTION) && (second->fraction.q == 1)){
		inttonum(dest, 1);
		for (i = 0; i< second->fraction.p; i++){
			numbermul(dest, dest, first);
		}
	}else{
		one = numtodouble(first);
		two = numtodouble(second);
		ldoubletonum(dest, exp(two * log(one)));
	}
}

Number * construct_number(){
	Number * tmp =  (Number *) GC_MALLOC(sizeof(Number));
	if(tmp == NULL){
		abort();
	}
}

char * numtostr(Number * number){
	char * temp;
	int z = 0; /*parenthesis*/
	char * to_return = GC_MALLOC(100); /*Should be enough*/
	if(to_return ==NULL){
		abort();
	}
	if (number->type == REAL){
		snprintf(to_return, 100, "%Lg", number->real);
	}else{
		if (number->fraction.p == 0){
			to_return[0] = '0';
			to_return[1] = '\0';
			return to_return;
		}
		temp = to_return;
		if (number->fraction.q != 1){
			*temp = '(';
			temp++;
			z = 1;
		}
		if(number->fraction.sign == NEGATIVE){
			if (!z){
				*temp = '(';
				temp++;
				z = 1;
			}
			*temp = '-';
			temp++;
		}
		snprintf(temp, 47, "%lli", number->fraction.p);
		while (*temp != '\0'){
			temp++;
		}
		if(number->fraction.q != 1){
			*temp = '/';
			temp ++;
			snprintf(temp, 47, "%lli", number->fraction.q);
			while (*temp != '\0'){
				temp++;
			}
		}
		if (z){
			*temp = ')';
			temp++;
		}
		*temp = '\0';
	}
	return to_return;
}

